// ==UserScript== // @name 咽沙学习小助手测试版V0.91 gpt3对话,解题 // @namespace http://tampermonkey.net/ // @version 0.91 // @description 创建一个明显可见且可拖动的聊天窗口,集成 OpenAI API 互动 // @author Your Name // @match *://*/* // @grant GM_addStyle // @grant GM_xmlhttpRequest // ==/UserScript== (function() { 'use strict'; const API_SECRET_KEY = "sk-zkix22izlS26fVKhfAtHnRa2A8rT9xZyQqAu6Sf8NfiXPPtdYEYN"; const BASE_URL = "https://model-bridge.okeeper.com/v1/"; // 添加 CSS 样式 - 确保样式定义正确 GM_addStyle(` /* 确保所有相关元素都有合适的样式 */ #button-container { width: 250px; height: 60px; /* 调整父容器高度 */ border-radius: 15px; font-size: 18px; position: fixed; right: 20px; bottom: 180px; /* 修改为距离底部180px */ z-index: 10000; cursor: pointer; font-family: "STKaiti", "华文楷体", "Kaiti", serif; overflow: visible; /* 确保子元素可以超出父容器 */ color: black; background: linear-gradient(135deg, #ffecb3, #ffdead); transition: all 0.3s ease-in-out; } .option-button { width: 250px; /* 增加宽度 */ height: 60px; /* 增加高度 */ border-radius: 15px; /* 设置圆角 */ border: none; background-color: #fff9db; /* 更浅的背景颜色 */ cursor: pointer; font-family: "STKaiti", "华文楷体", "Kaiti", serif; display: none; /* 默认隐藏 */ text-align: left; position: absolute; left: 0; top: -60px; /* 初始位置在父容器上方 */ opacity: 0; transition: all 0.3s ease-in-out; } #chat-window, #auto-answer-chat-window { width: 300px; height: 400px; /* 增加了聊天窗口的高度 */ background-color: #e6e6fa; /* 浅紫色背景 */ border: 1px solid #ccc; position: fixed; bottom: 60px; /* 修改了聊天窗口初始位置为距离底部60px */ right: 20px; z-index: 9999; box-shadow: 0 0 10px rgba(0,0,0,0.2); padding: 10px; border-radius: 5px; overflow: hidden; display: none; /* 初始隐藏 */ } #chat-content, #auto-answer-chat-content { height: calc(100% - 120px); /* 减去输入框、按钮和复制按钮的高度 */ overflow-y: auto; /* 允许上下滚动 */ } #input-container { position: absolute; bottom: 0; /* 紧贴聊天窗口底部 */ left: 0; right: 0; display: flex; justify-content: space-between; align-items: center; background-color: #fff; /* 区分聊天内容与输入区域 */ border-top: 1px solid #ccc; /* 分隔线 */ } #chat-input { width: calc(100% - 70px); /* 为发送按钮留出空间 */ height: 30px; margin: 5px; box-sizing: border-box; } #send-button { width: 50px; height: 30px; margin: 5px; box-sizing: border-box; } .copy-button { width: 100%; height: 30px; margin: 5px 0; box-sizing: border-box; background-color: #f0f0f0; border: 1px solid #ccc; cursor: pointer; text-align: center; line-height: 30px; } @keyframes gradientBG { 0% { background-color: #ffecb3; } 50% { background-color: #fffdd0; } 100% { background-color: #ffdead; } } button#show-hide-chat-button { animation: gradientBG 5s infinite; } `); function createButtonContainer() { const buttonContainer = document.createElement('div'); buttonContainer.id = 'button-container'; return buttonContainer; } function createParentButton(buttonContainer) { const parentButton = document.createElement('button'); parentButton.textContent = '咽沙学习小助手测试版 V0.91'; parentButton.style.width = '100%'; parentButton.style.height = '100%'; parentButton.style.border = 'none'; parentButton.style.backgroundColor = 'transparent'; parentButton.style.cursor = 'pointer'; parentButton.style.fontFamily = '"STKaiti", "华文楷体", "Kaiti", serif'; parentButton.style.fontSize = '18px'; buttonContainer.appendChild(parentButton); return parentButton; } function createOptionButtons(buttonContainer, chatWindowId) { for (let i = 0; i < 5; i++) { const optionButton = document.createElement('button'); optionButton.className = 'option-button'; if (i === 4) { // 对话GPT按钮 optionButton.textContent = '-对话GPT 【本按钮可切换窗口显示、隐藏】'; optionButton.onclick = () => toggleChatWindowVisibility('chat-window'); } else if (i === 1) { // 获取页面自动解答按钮 optionButton.textContent = '-获取页面自动解答'; optionButton.onclick = () => { toggleChatWindowVisibility('auto-answer-chat-window'); generateAutoAnswer(); }; } else { optionButton.onclick = () => parentButton.click(); // 收起子容器 } buttonContainer.appendChild(optionButton); } } function toggleChatWindowVisibility(chatWindowId) { const chatWindow = document.getElementById(chatWindowId); chatWindow.style.display = chatWindow.style.display === 'none' ? 'block' : 'none'; } // 改进获取页面文本逻辑,只提取可选中的文本节点 function getPageText() { let allText = ''; const texts = []; const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false); while (walker.nextNode()) { const node = walker.currentNode; if (node.parentElement.tagName !== 'SCRIPT' && node.parentElement.tagName !== 'STYLE') { texts.push(node.nodeValue.trim()); } } return texts.filter(text => text).join('\n'); // 过滤掉空字符串并连接成一个整体 } async function sendMessageToAI(message) { try { const response = await fetch(`${BASE_URL}chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${API_SECRET_KEY}` }, body: JSON.stringify({ model: "gpt-3.5-turbo", messages: [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": message} ] }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (!data.choices || !data.choices.length) { throw new Error('Invalid response from AI service.'); } return data; } catch (error) { console.error("Error fetching AI response:", error); throw error; } } function createChatWindow(isAutoAnswer) { const chatWindowId = isAutoAnswer ? 'auto-answer-chat-window' : 'chat-window'; const chatWindow = document.createElement('div'); chatWindow.id = chatWindowId; // 创建一个带有固定高度和滚动条的内容容器 const chatContentId = `${chatWindowId}-content`; const chatContent = document.createElement('div'); chatContent.id = chatContentId; chatContent.className = 'chat-content'; // 应用新的样式类 chatWindow.appendChild(chatContent); if (isAutoAnswer) { const instruction = document.createElement('div'); instruction.id = 'auto-answer-instruction'; instruction.textContent = '按下下方按钮,获取页面解答。(仅支持可被选中的文本)'; chatWindow.appendChild(instruction); const generateAnswerButton = document.createElement('button'); generateAnswerButton.id = 'generate-answer-button'; generateAnswerButton.textContent = '点击生成页面解答'; generateAnswerButton.onclick = async function() { generateAutoAnswer(); }; chatWindow.appendChild(generateAnswerButton); } else { // 对话GPT窗口逻辑 const inputContainer = document.createElement('div'); inputContainer.id = 'input-container'; const userInput = document.createElement('input'); userInput.type = 'text'; userInput.placeholder = '输入你的问题...'; inputContainer.appendChild(userInput); const sendButton = document.createElement('button'); sendButton.textContent = '发送'; sendButton.onclick = async function() { const userMessage = userInput.value.trim(); if (userMessage) { userInput.value = ''; const loadingMessage = document.createElement('div'); loadingMessage.textContent = '正在加载中…请耐心等待'; chatContent.appendChild(loadingMessage); chatContent.scrollTop = chatContent.scrollHeight; try { const apiResponse = await sendMessageToAI(userMessage); appendChatMessage(chatContent, '用户:', userMessage); appendChatMessage(chatContent, '回答:', apiResponse.choices[0].message.content); addCopyButton(chatContent, apiResponse.choices[0].message.content); // 移除加载提示 setTimeout(() => chatContent.removeChild(loadingMessage), 0); // 确保先插入新内容再移除加载提示 } catch (error) { const errorMessage = document.createElement('div'); errorMessage.textContent = '获取解答时出错,请稍后再试。'; chatContent.appendChild(errorMessage); chatContent.scrollTop = chatContent.scrollHeight; // 移除加载提示 setTimeout(() => chatContent.removeChild(loadingMessage), 0); // 确保先插入新内容再移除加载提示 } } }; inputContainer.appendChild(sendButton); chatWindow.appendChild(inputContainer); } document.body.appendChild(chatWindow); } function appendChatMessage(container, label, message) { const messageDiv = document.createElement('div'); messageDiv.innerHTML = `${label}
${message.replace(/\n/g, '
')}`; container.appendChild(messageDiv); container.scrollTop = container.scrollHeight; } function addCopyButton(container, content) { const copyButton = document.createElement('div'); copyButton.className = 'copy-button'; copyButton.textContent = '点击复制此次回答'; copyButton.onclick = () => navigator.clipboard.writeText(content).then(() => console.log('回答已复制到剪贴板')).catch(err => console.error('无法复制回答:', err)); container.appendChild(copyButton); container.scrollTop = container.scrollHeight; } function generateAutoAnswer() { // 获取正确的聊天内容容器ID const chatWindow = document.getElementById('auto-answer-chat-window'); const chatContentId = 'auto-answer-chat-content'; let chatContent = chatWindow.querySelector(`#${chatContentId}`); if (!chatContent) { // 如果未找到,则创建 chatContent = document.createElement('div'); chatContent.id = chatContentId; chatContent.className = 'chat-content'; // 应用新的样式类 chatWindow.insertBefore(chatContent, chatWindow.firstChild); // 插入到最顶部 } const pageText = getPageText(); const prompt = ` 提供一段包含问题及其选项的文本,请为每个问题进行编号,并组织成清晰的问题集。然后对每道题的每个选项进行详细解答,包括知识点分析、正确答案推导及错误选项解释。请使用中文整理并输出。 —— ${pageText} `; // 清空聊天记录并插入加载提示 chatContent.innerHTML = ''; // 清空聊天记录 const loadingMessage = document.createElement('div'); loadingMessage.textContent = '正在加载中…请耐心等待'; chatContent.appendChild(loadingMessage); chatContent.scrollTop = chatContent.scrollHeight; sendMessageToAI(prompt) .then(apiResponse => { // 移除加载提示 chatContent.removeChild(loadingMessage); // 添加AI回复内容 appendChatMessage(chatContent, '用户:', '获取页面自动解答'); appendChatMessage(chatContent, '回答:', apiResponse.choices[0].message.content); addCopyButton(chatContent, apiResponse.choices[0].message.content); }) .catch(error => { // 移除加载提示 chatContent.removeChild(loadingMessage); // 显示错误信息 const errorMessage = document.createElement('div'); errorMessage.textContent = '获取解答时出错,请稍后再试。'; chatContent.appendChild(errorMessage); chatContent.scrollTop = chatContent.scrollHeight; }); } const buttonContainer = createButtonContainer(); document.body.insertBefore(buttonContainer, document.body.firstChild); // 插入到body的第一个子元素之前 const parentButton = createParentButton(buttonContainer); let isExpanded = false; parentButton.onclick = function(event) { if (isExpanded) { hideButtons(); } else { showButtons(); } isExpanded = !isExpanded; }; function forceRedraw(element) { element.style.zIndex = 1; void element.offsetWidth; element.style.zIndex = ''; } function showButtons() { const buttons = Array.from(buttonContainer.querySelectorAll('.option-button')); const initialTopOffset = -60; // 初始偏移量,即父容器上方60px buttons.forEach((button, index) => { setTimeout(() => { button.style.display = 'block'; button.style.top = `${initialTopOffset - index * 60}px`; // 使用 top 定位,60px 是按钮的高度 button.style.opacity = '1'; forceRedraw(button); }, index * 200); // 每隔0.2秒显示下一个按钮 }); } function hideButtons() { const buttons = Array.from(buttonContainer.querySelectorAll('.option-button')).reverse(); buttons.forEach((button, index) => { setTimeout(() => { button.style.opacity = '0'; setTimeout(() => { button.style.display = 'none'; button.style.top = ''; // 清除定位 forceRedraw(button); }, 200); // 等待透明度动画完成后再隐藏按钮 }, index * 200); // 每隔0.2秒隐藏下一个按钮 }); } createOptionButtons(buttonContainer, 'chat-window'); createChatWindow(false); // 对话GPT窗口 createChatWindow(true); // 获取页面自动解答窗口 })();